package org.aspire.math.geom;

/******************************************************************************
 * A <code>Rectangle</code> specifies an area in a coordinate space that is 
 * enclosed by the <code>Rectangle</code> object's upper-left point 
 * {@code (x,y)} 
 * in the coordinate space, its width, and its height. 
 * <p>
 * This class is derived from J2SE java.awt package, to accommodate the other
 * environments, J2ME and Android, which do not have the said package.
 * 
 * @author JPACarabuena
 *****************************************************************************/
public class Rectangle implements Dimensioned, Positioned {

	// ========================================================================
	// Constants
	// ========================================================================

	// ========================================================================
	// Static Methods
	// ========================================================================

	// ========================================================================
	// Fields
	// ========================================================================

	/**************************************************************************
	 * The width and height of this rectangle.
	 *************************************************************************/
	private Dimension dimension;

	/**************************************************************************
	 * The (x,y) coordinate of this rectangle in the Cartesian plane.
	 *************************************************************************/
	private Point location;
	
	// ========================================================================
	// Constructors
	// ========================================================================

	/**************************************************************************
	 * Construct a new rectangle with the specified dimension and location.
	 * 
	 * @param dimension	the width and height of the rectangle.
	 * @param location	the location of the rectangle in the Cartesian plane.
	 *************************************************************************/
	public Rectangle(Dimension dimension, Point location) {
		this.dimension = dimension;
		this.location = location;
	}
	
	/**************************************************************************
	 * Construct a new rectangle with the default dimension and location.
	 *************************************************************************/
	public Rectangle() {
		this.dimension = new Dimension();
		this.location = new Point();
	}
	
	/**************************************************************************
	 * Construct a new rectangle with the specified dimension and default
	 * location.
	 * 
	 * @param dimension	the width and height of the rectangle.
	 *************************************************************************/
	public Rectangle(Dimension dimension) {
		this.dimension = dimension;
		this.location = new Point();
	}
	
	/**************************************************************************
	 * Construct a new rectangle with the specified location and default
	 * dimension.
	 * 
	 * @param location the location of the rectangle in the Cartesian plane.
	 *************************************************************************/
	public Rectangle(Point location) {
		this.dimension = new Dimension();
		this.location = location;
	}
	
	// ========================================================================
	// Getters & Setters
	// ========================================================================

	/**************************************************************************
	 * Returns the (x,y) coordinate of this rectangle.
	 * 
	 * @return the location of this rectangle.
	 *************************************************************************/
	public Point getLocation() {
		return this.location;
	}
	
	/**************************************************************************
	 * Returns the width and height of this rectangle.
	 * 
	 * @return the dimension of this rectangle.
	 *************************************************************************/
	public Dimension getDimension() {
		return this.dimension;
	}
	
	/**************************************************************************
	 * Sets the bounding rectangle for this rectangle. This includes the width
	 * and height as well as the location in the Cartesian plane.
	 * 
	 * @param dimension	the new dimension.
	 * @param location the new location.
	 *************************************************************************/
	public void setBounds(Dimension dimension, Point location) {
		this.dimension = dimension;
		this.location = location;
	}
	
	/**************************************************************************
	 * Sets the location of this rectangle to the specified point.
	 * 
	 * @param location	the new location.
	 *************************************************************************/
	public void setLocation(Point location) {
		this.location = location;
	}
	
	/**************************************************************************
	 * Sets the location of this rectangle to the specified x and y coordiante.
	 * 
	 * @param x	the new x-coordinate.
	 * @param y the new y-coordinate.
	 *************************************************************************/
	public void setLocation(int x, int y) {
		this.location.setLocation(x, y);
	}
	
	/**************************************************************************
	 * Sets the dimension of this rectangle to the specified size.
	 * 
	 * @param dimension	the new dimension.
	 *************************************************************************/
	public void setDimension(Dimension dimension) {
		this.dimension = dimension;
	}
	
	/**************************************************************************
	 * Sets the dimension of this rectangle to the specified width and height.
	 * 
	 * @param width	the new width.
	 * @param height the new height.
	 *************************************************************************/
	public void setDimension(int width, int height) {
		this.dimension.setSize(width, height);
	}
	
	// ========================================================================
	// Override Methods
	// ========================================================================

	@Override
	public int getWidth() {
		return this.dimension.getWidthInt();
	}

	@Override
	public int getHeight() {
		return this.dimension.getHeightInt();
	}

	@Override
	public double getX() {
		return this.location.getXInt();
	}

	@Override
	public double getY() {
		return this.location.getYInt();
	}

	@Override
	public void setX(double x) {
		this.location.setX(x);
	}
	
	@Override
	public void setY(double y) {
		this.location.setY(y);
	}
	
	@Override
	public void setWidth(int width) {
		this.dimension.setWidth(width);
	}
	
	@Override
	public void setHeight(int height) {
		this.dimension.setHeight(height);
	}
	// ========================================================================
	// Methods
	// ========================================================================

	/**************************************************************************
	 * Checks if this rectangle entirely contains the specified rectangle.
	 * 
	 * @param r	the specified rectangle.
	 * @return whether or not the specified rectangle is inside this rectangle.
	 *************************************************************************/
	public boolean contains(Rectangle r) {
		int w = this.getWidth();
		int h = this.getHeight();
		int rw = r.getWidth();
		int rh = r.getHeight();
		
		if((w | h | rw | rh) < 0) {
			return false;
		}
		
		int x = (int) this.getX();
		int y = (int) this.getY();
		
		if(r.getX() < x || r.getY() < y) {
			return false;
		}
		
		w += x;
		rw += r.getX();
		
		if(rw <= r.getX()) {
			if(w >= x || rw > w) return false;
		} else {
			if(w >= x && rw > w) return false;
		}
		
		h += y;
		rh += r.getY();
		
		if(rh <= r.getY()) {
			if(h >= y || rh > h) return false;
		} else {
			if(h >= y && rh > h) return false;
		}
		
		return true;
	}
	
	/**************************************************************************
	 * Checks if this rectangle contains the specified point.
	 * 
	 * @param p	the specified point.
	 * @return whether or not this rectangle contains specified point.
	 *************************************************************************/
	public boolean contains(Point p) {
		int w = this.getWidth();
		int h = this.getHeight();
		
		if((w | h) < 0) {
			return false;
		}
		
		int x = (int) this.getX();
		int y = (int) this.getY();
		
		if(p.x < x || p.y < y) {
			return false;
		}
		
		w += x;
		h += y;
		
		return ((w < x || w > p.x) && (h < y || h > p.y));
	}
	
	/**************************************************************************
	 * Checks if the specified rectangle intersects with this rectangle.
	 * 
	 * @param r	the specified rectangle.
	 * @return	whether or not this rectangle intersects the specified 
	 * 			rectangle.
	 *************************************************************************/
	public boolean intersects(Rectangle r) {
		int tw = this.getWidth();
		int th = this.getHeight();
		int rw = r.getWidth();
		int rh = r.getHeight();
		
		if(rw <= 0 || rh <= 0 || tw <= 0 || th <= 0) {
			return false;
		}
		
		int tx = (int) this.getX();
		int ty = (int) this.getY();
		int rx = (int) r.getX();
		int ry = (int) r.getY();
		
		rw += rx;
		rh += ry;
		tw += tx;
		th += ty;
		
		return ((rw < rx || rw > tx) &&
				(rh < ry || rh > ty) &&
				(tw < tx || tw > rx) &&
				(th < ty || th > ry));
	}
	
	/**************************************************************************
	 * Returns the intersection between this rectangle and the specified one.
	 * 
	 * @param r	the specified rectangle.
	 * @return	the intersection between this and the specified rectangle.
	 *************************************************************************/
	public Rectangle intersection(Rectangle r) {
		int tx1 = (int) this.getX();
		int ty1 = (int) this.getY();
		int rx1 = (int) r.getX();
		int ry1 = (int) r.getY();
		long tx2 = tx1; tx2 += this.getWidth();
		long ty2 = ty1; ty2 += this.getHeight();
		long rx2 = rx1; rx2 += r.getWidth();
		long ry2 = ry1; ry2 += r.getHeight();
		if (tx1 < rx1) tx1 = rx1;
		if (ty1 < ry1) ty1 = ry1;
		if (tx2 > rx2) tx2 = rx2;
		if (ty2 > ry2) ty2 = ry2;
		tx2 -= tx1;
		ty2 -= ty1;
		// tx2,ty2 will never overflow (they will never be
		// larger than the smallest of the two source w,h)
		// they might underflow, though...
		if (tx2 < Integer.MIN_VALUE) tx2 = Integer.MIN_VALUE;
		if (ty2 < Integer.MIN_VALUE) ty2 = Integer.MIN_VALUE;
		
		return new Rectangle(new Dimension(tx2, ty2), new Point(tx1, ty1));
	}
	
	/**************************************************************************
	 * Returns the union between this rectangle and the specified one.
	 * 
	 * @param r	the specified rectangle.
	 * @return the union between this and the specified rectangle.
	 *************************************************************************/
	public Rectangle union(Rectangle r) {
		long tx2 = this.getWidth();
		long ty2 = this.getHeight();
		
		if((tx2 | ty2) < 0) {
			return new Rectangle(r.getDimension(), r.getLocation());
		}
		
		long rx2 = r.getWidth();
		long ry2 = r.getHeight();
		
		if((rx2 | ry2) < 0) {
			return new Rectangle(this.getDimension(), this.getLocation());
		}
		
		int tx1 = (int) this.getX();
		int ty1 = (int) this.getY();
		
		tx2 += tx1;
		ty2 += ty1;
		
		int rx1 = (int) r.getX();
		int ry1 = (int) r.getY();
		
		rx2 += rx1;
		ry2 += ry1;
		
		if(tx1 > rx1) tx1 = rx1;
		if(ty1 > ry1) ty1 = ry1;
		if(tx2 < rx2) tx2 = rx2;
		if(ty2 < ry2) ty2 = ry2;
		
		tx2 -= tx1;
		ty2 -= ty1;
		
		if(tx2 > Integer.MAX_VALUE) tx2 = Integer.MAX_VALUE;
		if(ty2 > Integer.MAX_VALUE) ty2 = Integer.MAX_VALUE;
		
		return new Rectangle(new Dimension(tx2, ty2), new Point(tx1, ty1));
	}
	
	// ========================================================================
	// Inner & Anonymous Classes
	// ========================================================================
}